Spring AOP 编程理解
一、AOP的理解
最近在学习Spring,理解涉及到Spring的两大核心部分,一个是IOC(控制反转),另一个就是AOP(面向切面编程),今天主要讲一下什么是面向切面编程?
在知乎上看到别人的回答,有句话觉得特别的有道理,“这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程”。
到底什么是AOP呢?就是把逻辑代码和处理繁琐事务的代码分离开,以便能够分离复杂度,让人在同一时间只用思考代码逻辑,或者琐碎事务,代码逻辑比如插入一条数据,那么琐碎事务就包括获取连接和关闭连接,事务开始,事务提交。
一般而言,我们将切入到指定类指定方法的代码片称为切面,而切入到哪些类、哪些方法则叫切入点,有了AOP,就可以将几个类共有的代码抽取到一个切片中,等到需要的时候再切入到对象中取,从而改变原有的行为。
举个例子:
先假设你有一段逻辑代码要写,在这段代码之前要写log,代码完成之后要写log,结局就是一大堆的log淹没了逻辑代码,而AOP的思想就是将非逻辑部分的代码抽离出来,只考虑逻辑代码就行了。
和AOP对应的是OOP,对于OOP,其为从横向上区分出一个个的类,而AOP从纵向上向对象中加入特定的代码。
Spring实现的AOP是代理模式(后面会介绍一下代理模式),给调用者实际使用的是已经加工过的对象。
二、动态代理
假设有这样几个类:
ArithmeticCalculator.java
1 2 3 4 5 6 7
| public interface ArithmeticCalculator { int add(int i, int j); int sub(int i, int j); int mul(int i, int j); int div(int i, int j); }
|
其有一个实现类:
ArithmeticCalculatorImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class ArithmeticCalculatorImpl implements ArithmeticCalculator { public int add(int i, int j) { int result = i + j; return result; } public int sub(int i, int j) { int result = i - j; return result; } public int mul(int i, int j) { int result = i * j; return result; } public int div(int i, int j) { int result = i / j; return result; } }
|
若现在想实现这样的功能:在程序执行期间追踪正在发生的活动,此时代码将变为:
ArithmeticCalculatorLoggingImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class ArithmeticCalculatorLoggingImpl implements ArithmeticCalculator { public int add(int i, int j) { System.out.println("The method add begins with[" + i + "," + j + "]"); int result = i + j; System.out.println("The method add ends with " + result); return result; } public int sub(int i, int j) { System.out.println("The method sub begins with[" + i + "," + j + "]"); int result = i - j; System.out.println("The method sub ends with " + result); return result; } public int mul(int i, int j) { System.out.println("The method mul begins with[" + i + "," + j + "]"); int result = i * j; System.out.println("The method mul ends with " + result); return result; } public int div(int i, int j) { System.out.println("The method div begins with[" + i + "," + j + "]"); int result = i / j; System.out.println("The method div ends with " + result); return result; } }
|
这样大量的log就淹没了业务逻辑代码,并且代码存在大量的重复,且如果要修改日志,此必须对每一处进行修改,显然这不是一种好的解决方法。
这时候动态代理就可以粉墨登场了!!!
ArithmeticCalculatorLoggingProxy.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class ArithmeticCalculatorLoggingProxy { private ArithmeticCalculator target; public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) { this.target = target; } public ArithmeticCalculator getLoggingProxy() { ArithmeticCalculator proxy = null; ClassLoader loader = target.getClass().getClassLoader(); Class[] interfaces = new Class[]{ArithmeticCalculator.class}; InvocationHandler h = new InvocationHandler() { * proxy: 正在返回的那个代理对象,一般情况下,在invoke方法中不使用该对象 * method:正在被调用的方法 * args: 调用方法时传入的参数 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); System.out.println("The method " + methodName + "begins with " + Arrays.asList(args)); Object result = method.invoke(target, args); System.out.println("The method " + methodName + "ends with " + result); return result; } }; proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, h); return proxy; } }
|
而此时的main函数如下:
1 2 3 4 5 6 7 8 9 10 11
| public class Main { public static void main(String[] args) { ArithmeticCalculator target = new ArithmeticCalculatorImpl(); ArithmeticCalculator proxy = new ArithmeticCalculatorLoggingProxy(target).getLoggingProxy(); int result = proxy.add(1, 2); System.out.println("-->" + result); result = proxy.div(4, 2); System.out.println("-->" + result); } }
|
此时,ArithmeticCalculatorImpl中的函数全部都是业务逻辑,而与业务逻辑无关的代码均在代理中,若想修改业务逻辑无关代码,只需要在ArithmeticCalculatorLoggingProxy中进行修改即可,而无需修改ArithmeticCalculatorImpl中的代码!